package filter

import (
	"bytes"
	"strings"
	"unicode"
)

// String 单行文本方式过滤字符串，保留Emoji表情符号、标点符号、内部空格（允许含有一个空格, 连续空格替换为一个空格)
//   - 过滤: 左右任意空字符
//   - 过滤: 全部换行符与制表符
//   - 过滤: 全部HTML标签(StripTags)
func String(s string) string {
	s = strings.TrimSpace(s)
	if len(s) < 1 {
		return ""
	}
	var buf bytes.Buffer
	buf.Grow(len(s))
	isSpace := false
	for _, v := range StripTags(s) {
		switch v {
		case '\n', '\r', '\t':
			//不允许换行与制表符
			continue
		case ' ', '　':
			//最多允许一个空格
			if isSpace {
				continue
			}
			isSpace = true
			//空格比较特殊,全角为 12288,半角为 32
			buf.WriteRune(' ')
		default:
			isSpace = false
			buf.WriteRune(v)
		}
	}
	return strings.TrimSpace(buf.String())
}

// StringStrict 单行文本方式/严格模式过滤字符串，保留内部空格（允许含有一个空格, 连续空格替换为一个空格)
//   - 过滤: String + 全部Emoji表情符号 + 全部标点符号
func StringStrict(s string) string {
	s = strings.TrimSpace(s)
	if len(s) < 1 {
		return ""
	}
	return String(StripAllPunc(StripEmoji(s)))
}

// Text 多行文本方式过滤字符串，保留标点符号、Emoji表情符号、内部换行符/制表符、空格(同一位置不允许多个连续的换行符或空格，仅保留一个不连续的换行符或空格)
//   - 过滤: 左右任意空字符
//   - 过滤: 全部HTML标签(StripTags)
func Text(s string) string {
	s = strings.TrimSpace(s)
	if len(s) < 1 {
		return ""
	}
	var b bytes.Buffer
	isLine, isSpace := false, false
	for _, v := range s {
		switch v {
		case '\n', '\r', '\t':
			if isLine {
				continue
			}
			isLine = true
			isSpace = false
			b.WriteRune('\n')
		case ' ', '　': //半角/全角空格
			if isSpace {
				continue
			}
			isLine = false
			isSpace = true
			//空格比较特殊,全角为 12288,半角为 32
			b.WriteRune(' ')
		default:
			//排除空字符
			if !unicode.IsSpace(v) {
				b.WriteRune(v)
			}
			isLine = false
			isSpace = false
		}
	}
	return strings.TrimSpace(StripTags(b.String()))
}

// TextStrict 多行文本方式/严格模式过滤字符串，保留标点符号，内部空格(同一位置不允许多个连续的换行符或空格，仅保留一个不连续的换行符或空格)
//   - 过滤: Text + 全部Emoji表情符号 + 全部换行符与制表符
func TextStrict(s string) string {
	return strings.ReplaceAll(Text(StripEmoji(s)), "\n", "")
}

// Code 多行文本代码模式过滤字符串，去掉左右空字符，保留内部换行符、空格及TAB(制表符)
//   - 同一位置不允许多个连续的换行符
func Code(s string) string {
	s = strings.TrimSpace(s)
	if len(s) < 1 {
		return ""
	}
	var buf bytes.Buffer
	buf.Grow(len(s))
	isLine := false
	for _, v := range s {
		switch v {
		case '\n', '\r':
			if isLine {
				continue
			}
			buf.WriteRune('\n')
			isLine = true
		case '　': //全角空格转为半角空格
			buf.WriteRune(' ')
			isLine = false
		case '\t', ' ': //TAB(制表符)/半角空格原样插入
			buf.WriteRune(v)
			isLine = false
		default:
			//排除空字符
			if !unicode.IsSpace(v) {
				buf.WriteRune(v)
			}
			isLine = false
		}
	}
	return strings.TrimSpace(buf.String())
}

// StripSpace 去掉任意位置的空字符
func StripSpace(s string) string {
	s = strings.TrimSpace(s)
	if len(s) < 1 {
		return ""
	}
	//去掉全部空字符
	var b bytes.Buffer
	b.Grow(len(s))
	for _, v := range s {
		//半角/全角空格及其他空字符
		if v != '　' && !unicode.IsSpace(v) {
			b.WriteRune(v)
		}
	}
	return b.String()
}

// StripWith 去掉任意位置的指定字符(多个字符按Rune遍历处理)
func StripWith(s string, chars string) string {
	if len(s) == 0 || len(chars) == 0 {
		return s
	}
	var buf bytes.Buffer
	buf.Grow(len(s))
	for _, v := range s {
		if !strings.ContainsRune(chars, v) {
			buf.WriteRune(v)
		}
	}
	return buf.String()
}

// StripTags 过滤HTML标签
func StripTags(s string) string {
	if len(strings.TrimSpace(s)) == 0 {
		return s
	}
	var (
		buf bytes.Buffer
		tag bytes.Buffer
	)
	start, end := false, true
	for _, v := range s {
		if start {
			if v == '>' {
				start = false
				end = true
				continue
			} else {
				// 65-90（A-Z），97-122（a-z）
				// 将tag标签内的大写字母转换为小写字母
				if v >= 65 && v <= 90 {
					tag.WriteRune(v + 32)
				} else {
					//清理标签内的半角/全角空格及其他空字符
					if v != '　' && !unicode.IsSpace(v) {
						tag.WriteRune(v)
					}
				}
			}
		} else {
			if v == '<' {
				start = true
				end = false
				tag.Reset()
				continue
			}
		}
		if end {
			if tag.Len() > 3 {
				t := strings.ReplaceAll(tag.String(), "\\0", "")
				if strings.Contains(t, "java") || strings.Contains(t, "script") || strings.Contains(t, "frame") || strings.Contains(t, "expression") {
					continue
				}
			}
			buf.WriteRune(v)
		}
	}
	str := buf.String()
	left, right := strings.IndexRune(str, '<'), strings.IndexRune(str, '>')
	if left >= 0 && right > left {
		return StripTags(str)
	}
	return str
}

// StripChinese 过滤掉中文字符
//   - Todo 中文标点符号未处理
func StripChinese(s string) string {
	if len(strings.TrimSpace(s)) == 0 {
		return s
	}
	var b bytes.Buffer
	for _, v := range s {
		if !unicode.Is(unicode.Han, v) {
			b.WriteRune(v)
		}
	}
	return b.String()
}

// OnlyChinese 仅保留中文，过滤掉其他字符及任意符号
func OnlyChinese(s string) string {
	s = StripAllPunc(s)
	s = StripEmoji(s)
	s = StripSpace(s)
	if len(s) < 1 {
		return ""
	}
	var b bytes.Buffer
	for _, v := range s {
		if unicode.Is(unicode.Han, v) {
			b.WriteRune(v)
		}
	}
	return b.String()
}

// OnlyNumber 仅保留数字(含+-符号)，过滤掉其他字符及任意符号
func OnlyNumber(s string) string {
	s = StripSpace(s)
	if len(s) < 1 {
		return ""
	}
	for strings.Contains(s, "..") {
		s = strings.ReplaceAll(s, "..", ".")
	}
	// ASCII 43 DEC +
	// ASCII 45 DEC -
	// ASCII 46 DEC .
	// ASCII 48---57  0---9
	var b bytes.Buffer
	var isDot = false
	var ls = len([]rune(s))
	for i, v := range s {
		if ls >= i+1 {
			if v == 43 || v == 45 {
				// +-符号仅允许出现在开头的数字前
				if s[i+1] >= 48 && s[i+1] <= 57 {
					b.WriteRune(v)
				}
				isDot = false
				continue
			} else if i != 0 &&
				!isDot &&
				v == 46 &&
				s[i-1] >= 48 && s[i-1] <= 57 &&
				s[i+1] >= 48 && s[i+1] <= 57 {
				//Dot符号上一个字符必须是数字，下一个字符必须是数字
				b.WriteRune(v)
				isDot = true
				continue
			}
		}
		isDot = false
		if v < 48 {
			continue
		}
		if v <= 57 {
			b.WriteRune(v)
			continue
		}
	}
	return b.String()
}

// OnlyAlpha 仅保留字母A-Za-z，过滤掉其他字符及任意符号
func OnlyAlpha(s string) string {
	s = StripSpace(s)
	if len(s) < 1 {
		return ""
	}
	// ASCII 65---90  A-Z
	// ASCII 97---122 a-z
	var b bytes.Buffer
	for _, v := range s {
		if v < 65 || v > 122 {
			continue
		}
		if v > 90 && v < 97 {
			continue
		}
		b.WriteRune(v)
	}
	return b.String()
}

// OnlyAlphaNumber 仅保留字母A-Za-z数字0-9(含+-符号)，过滤掉其他字符及任意符号
func OnlyAlphaNumber(s string) string {
	s = StripSpace(s)
	if len(s) < 1 {
		return ""
	}
	for strings.Contains(s, "..") {
		s = strings.ReplaceAll(s, "..", ".")
	}

	// ASCII 43 DEC +
	// ASCII 45 DEC -
	// ASCII 46 DEC .
	// ASCII 48---57  0---9
	// ASCII 65---90  A-Z
	// ASCII 97---122 a-z

	var b bytes.Buffer
	var isDot = false
	var ls = len([]rune(s))
	for i, v := range s {
		if ls >= i+1 {
			if v == 43 || v == 45 {
				// +-符号仅允许出现在开头的数字前
				if s[i+1] >= 48 && s[i+1] <= 57 {
					b.WriteRune(v)
				}
				isDot = false
				continue
			} else if i != 0 &&
				!isDot &&
				v == 46 &&
				s[i-1] >= 48 && s[i-1] <= 57 &&
				s[i+1] >= 48 && s[i+1] <= 57 {
				//Dot符号上一个字符必须是数字，下一个字符必须是数字
				b.WriteRune(v)
				isDot = true
				continue
			}
		}

		isDot = false
		if v < 48 {
			continue
		}
		if v <= 57 {
			b.WriteRune(v)
			continue
		}
		if v < 65 || v > 122 {
			continue
		}
		if v <= 90 || (v >= 97 && v <= 122) {
			b.WriteRune(v)
		}
	}
	return b.String()
}
